Skip to content

gh-143756: Fix potential data race in SSLContext.load_cert_chain#143818

Merged
colesbury merged 2 commits into
python:mainfrom
colesbury:gh-143756-load_cert_chain
Jan 22, 2026
Merged

gh-143756: Fix potential data race in SSLContext.load_cert_chain#143818
colesbury merged 2 commits into
python:mainfrom
colesbury:gh-143756-load_cert_chain

Conversation

@colesbury

@colesbury colesbury commented Jan 13, 2026

Copy link
Copy Markdown
Contributor

Concurrent calls to load_cert_chain caused data races in OpenSSL code.

Concurrent calls to `load_cert_chain` caused data races in OpenSSL code.
@colesbury colesbury marked this pull request as ready for review January 13, 2026 22:06

@picnixz picnixz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need tests for that actually?

Comment thread Modules/_ssl.c
do { (save) = PyEval_SaveThread(); } while(0)
#define PySSL_END_ALLOW_THREADS_S(save) \
do { PyEval_RestoreThread(save); _PySSL_FIX_ERRNO; } while(0)
#define PySSL_BEGIN_ALLOW_THREADS(self) { \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While you are it, can you use a do-while construct? (here we would just have the do {) In addition can you make it multiline with line continuations aligned on a tab multiple? (like PEP-7) TiA.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Comment thread Modules/_ssl.c
do { (save) = PyEval_SaveThread(); PyMutex_Lock(mutex); } while(0)
#define PySSL_END_ALLOW_THREADS_S(save, mutex) \
do { PyMutex_Unlock(mutex); PyEval_RestoreThread(save); _PySSL_FIX_ERRNO; } while(0)
#define PySSL_BEGIN_ALLOW_THREADS_S(save) \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use uppercase letters for the macro parameters please?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not do a bunch of extra refactoring in a bugfix PR

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right you want to backport this. I think there will be anyway conflicts so yeah let's not make it more complicate

Comment thread Modules/_ssl.c
Comment on lines +4634 to +4635
if (keyfile == Py_None)
keyfile = NULL;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be added in the if (keyfile) check instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto; this is existing code

Comment thread Modules/_ssl.c
if (password != Py_None) {
if (PyCallable_Check(password)) {
pw_info.callable = password;
} else if (!_pwinfo_set(&pw_info, password,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} else if (!_pwinfo_set(&pw_info, password,
}
else if (!_pwinfo_set(&pw_info, password,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Comment thread Modules/_ssl.c
if (PyCallable_Check(password)) {
pw_info.callable = password;
} else if (!_pwinfo_set(&pw_info, password,
"password should be a string or callable")) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And realign this adter putting the elseif on its own line

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Comment thread Modules/_ssl.c
if (!PyUnicode_FSConverter(certfile, &certfile_bytes)) {
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
PyErr_SetString(PyExc_TypeError,
"certfile should be a valid filesystem path");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a follow-up: This is a bit misleading. For me such message usually implies a ValueError instead of TypeError. Do we use this formulation for other occurrences of FSConverter?

Maybe: "expecting a path-like object for 'certfile', got ..."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

@colesbury

Copy link
Copy Markdown
Contributor Author

Tests already exist. #143752 runs it with TSan

@picnixz picnixz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry as I was on mobile I didn't see that it was just moving code around. We can refactor this later (I really want to split ssl.c because it is messy to maintain but it is also annoying to create more files...)

@colesbury colesbury merged commit bcf9cb0 into python:main Jan 22, 2026
55 checks passed
@colesbury colesbury deleted the gh-143756-load_cert_chain branch January 22, 2026 19:02
thunder-coding pushed a commit to thunder-coding/cpython that referenced this pull request Feb 15, 2026
pythongh-143818)

Concurrent calls to `load_cert_chain` caused data races in OpenSSL code.
kumaraditya303 added a commit to kumaraditya303/cpython that referenced this pull request Jul 10, 2026
Concurrent calls to SSLContext.load_cert_chain on the same context
race on the SSL_CTX default password callback. This is fixed in 3.15
(pythonGH-143818), but under TSAN with instrumented OpenSSL the race is now
reported on 3.14, so skip the test that triggers it.
kumaraditya303 added a commit to kumaraditya303/cpython that referenced this pull request Jul 10, 2026
This test spawns 8 threads calling load_cert_chain on the same
context, directly triggering the data race on the SSL_CTX default
password callback (fixed in 3.15 by pythonGH-143818). It was the test
that failed both TSan CI jobs.
kumaraditya303 added a commit that referenced this pull request Jul 10, 2026
…53355)

Compile OpenSSL with TSan in the TSan CI job so that data races between
Python code and OpenSSL internals are visible to the sanitizer.

Also skip test_ssl_in_multiple_threads under TSan: concurrent calls to
SSLContext.load_cert_chain on the same context race on the SSL_CTX
default password callback. The race is fixed in 3.15+ (GH-143818), but
with TSan-instrumented OpenSSL it is now reported on 3.14.

(cherry picked from commit fc19ad7)

Co-authored-by: Sam Gross <colesbury@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants